<template>
	<el-form ref="cFormRef" v-loading="loading" :model="modelValue" :label-width="labelWidth" @submit.prevent>
		<!-- !dictLoading 是用来避免字典还没查出 _dictData就给组件了 -->
		<CFormItemList v-if="itemList && !dictLoading" :model="modelValue" :item-list="itemList" :label-suffix="labelSuffix">
			<!-- 递归传递插槽！！！ -->
			<template v-for="slotName in Object.keys($slots)" #[slotName]="scope">
				<slot :name="slotName" v-bind="scope" />
			</template>
			<template #default="{ item, data, prop }">
				<component :is="componentInfo" v-bind="{ item, data, prop }"></component>
			</template>
		</CFormItemList>
	</el-form>
</template>
<script lang="tsx">
export const PROVIDE_TOKEN = 'PROVIDE_TOKEN'
export type CFormProvide = {
	formData: Ref<Record<string, any>>
}
</script>
<script setup lang="tsx" name="CForm">
import { useVmodel } from '@/hooks/useVmodel'
import { FormItemProps, CompType } from './interface'
import { ref, computed, watch, nextTick, provide, Ref } from 'vue'
import dict from '@/utils/dict/index'
import { deepClone } from '@/utils/util'
import { ElMessage, FormInstance } from 'element-plus'
import CFormItemList from './components/CFormItemList/index.vue'
import { isArray } from '@/utils/is'
import TinyMCE from '@/components/TinyMCE/index.vue'
const emits = defineEmits([
	'update:modelValue',
	'loadingChange',
	'submitLoadingChange',
	'submitSuccess',
	'submitFail',
	'submitComplete',
	'reset'
])
interface CFormProps {
	itemList: FormItemProps[] // 配置项目列表
	modelValue: Record<string, any> // 数据
	labelWidth?: string // label 宽度
	labelSuffix?: string // 表单域标签的后缀
	apiSubmit?: (...params: any[]) => Promise<any> // 提交 api
}
// 默认值
const props = withDefaults(defineProps<CFormProps>(), {
	labelWidth: '120px'
})
// vmodel
const modelValue = useVmodel<Record<string, any>>(props)

// 字典加载
const dictLoading = ref(true)
// 如果当前 dict为后台数据 需要请求数据，则调用该请求接口，获取 dict数据
const apiArr: Promise<any>[] = []
function deepDict(itemInfo?: Partial<FormItemProps> | Partial<FormItemProps>[]) {
	if (!itemInfo) {
		return
	} else if (!isArray(itemInfo)) {
		itemInfo = [itemInfo]
	}
	for (const item of itemInfo as Partial<FormItemProps>[]) {
		if (item.dict) {
			if (typeof item.dict === 'function') {
				apiArr.push(item.dict().then(data => (item._dictData = data)))
			} else if (typeof item.dict === 'string') {
				apiArr.push(dict.$cache.getDict(item.dict).then(data => (item._dictData = data)))
			} else {
				item._dictData = item.dict
			}
		}
		deepDict(item.itemInfo)
	}
}
// 递归获取异步字典请求
deepDict(props.itemList)
// 重置时用到的数据
let resetData: any
// dictLoading.value = true
Promise.all(apiArr)
	.then(() => {})
	// .catch(() => {
	// 	ElMessage.error('字典接口异常')
	// })
	.finally(() => {
		dictLoading.value = false
		nextTick(() => {
			// 复制当前表单数据，用在 reset方法中
			resetData = deepClone(modelValue.value)
		})
	})

/** 提交 */
const submitLoading = ref(false)
watch(submitLoading, val => {
	emits('submitLoadingChange', val)
})
function submit() {
	getFormRef()
		?.validate()
		.then(() => {
			if (props.apiSubmit) {
				const params = getFormDataCopy()
				submitLoading.value = true
				props
					.apiSubmit(params)
					.then(res => {
						submitLoading.value = false
						ElMessage.success({ message: `提交成功！` })
						// 提交成功
						nextTick(() => {
							emits('submitSuccess', res)
						})
						// 继续往下抛
						return res
					})
					.catch(e => {
						submitLoading.value = false
						nextTick(() => {
							emits('submitFail', e)
						})
						// 继续往下抛
						throw e
					})
					.finally(() => {
						emits('submitComplete')
					})
			}
		})
		.catch(() => {})
}

/* 组件信息 */
const componentInfo = ({ item, data, prop }: { data: Record<string, any>; item: FormItemProps; prop: any }): any => {
	const { _dictData, compType, compProps } = item
	/* 组件映射，此对象放里面方便后面根据 item 数据修改*/
	const compMap: Record<CompType, () => any> = {
		array() {},
		object() {},
		text() {
			if (_dictData) {
				return <dict-text value={data[prop]} options={_dictData} {...compProps}></dict-text>
			}
			return <span>{data[prop]}</span>
		},
		tag() {
			if (_dictData) {
				return <dict-tag value={data[prop]} options={_dictData} {...compProps}></dict-tag>
			}
			return <el-tag {...compProps}>{data[prop]}</el-tag>
		},
		alert() {
			return (
				<el-alert type="info" show-icon closable={false} {...compProps}>
					{data[prop]}
				</el-alert>
			)
		},
		input() {
			return <el-input v-model={data[prop]} placeholder="请输入" clearable {...compProps}></el-input>
		},
		textarea() {
			return (
				<el-input
					v-model={data[prop]}
					placeholder="请输入"
					type="textarea"
					clearable
					autosize={{ minRows: 2, maxRows: 4 }}
					{...compProps}
				/>
			)
		},
		number() {
			return (
				<el-input-number
					v-model={data[prop]}
					min={1}
					placeholder="请输入"
					controlsPosition="right"
					{...compProps}
				></el-input-number>
			)
		},
		switch() {
			return <el-switch v-model={data[prop]} {...compProps}></el-switch>
		},
		select() {
			return <dict-select v-model={data[prop]} options={_dictData} placeholder="请选择" clearable {...compProps}></dict-select>
		},
		radio() {
			return <dict-radio v-model={data[prop]} options={_dictData} {...compProps}></dict-radio>
		},
		multipleSelect() {
			return (
				<dict-select
					v-model={data[prop]}
					options={_dictData}
					placeholder="请选择"
					clearable
					multiple
					{...compProps}
				></dict-select>
			)
		},
		treeSelect() {
			return <el-tree-select v-model={data[prop]} placeholder="请选择" clearable {...compProps}></el-tree-select>
		},
		multipleTreeSelect() {
			return <el-tree-select v-model={data[prop]} placeholder="请选择" clearable multiple {...compProps}></el-tree-select>
		},
		date() {
			return (
				<el-date-picker
					v-model={data[prop]}
					type="date"
					valueFormat="YYYY-MM-DD"
					placeholder="请选择日期"
					clearable
					{...compProps}
				></el-date-picker>
			)
		},
		daterange() {
			return (
				<el-date-picker
					v-model={data[prop]}
					type="daterange"
					valueFormat="YYYY-MM-DD"
					rangeSeparator="至"
					startPlaceholder="开始时间"
					endPlaceholder="结束时间"
					clearable
					{...compProps}
				></el-date-picker>
			)
		},
		time() {
			return <el-time-picker v-model={data[prop]} placeholder="请选择时间" clearable {...compProps}></el-time-picker>
		},
		timerange() {
			return (
				<el-time-picker
					v-model={data[prop]}
					isRange
					rangeSeparator="至"
					startPlaceholder="开始时间"
					endPlaceholder="结束时间"
					clearable
					{...compProps}
				></el-time-picker>
			)
		},
		datetime() {
			return (
				<el-date-picker
					v-model={data[prop]}
					type="datetime"
					valueFormat="YYYY-MM-DD HH:mm:ss"
					placeholder="结束时间"
					clearable
					{...compProps}
				></el-date-picker>
			)
		},
		datetimerange() {
			return (
				<el-date-picker
					v-model={data[prop]}
					type="datetimerange"
					valueFormat="YYYY-MM-DD HH:mm:ss"
					rangeSeparator="至"
					startPlaceholder="开始时间"
					endPlaceholder="结束时间"
					clearable
					{...compProps}
				></el-date-picker>
			)
		},
		tinyMCE() {
			return <TinyMCE v-model={data[prop]} {...compProps} />
		}
	}

	let result
	if (compType) {
		result = compMap[compType]()
	} else {
		// 默认
		result = compMap.input()
	}
	// // 子节点
	// if (childrenList && childrenList.length) {
	// 	componentInfo()
	// }

	return <>{result}</>
}
// 加载中
const loading = computed(() => {
	return dictLoading.value
})
watch(loading, val => {
	emits('loadingChange', val)
})

// 获取表单引用
const cFormRef = ref<FormInstance>()
function getFormRef() {
	return cFormRef.value
}
// 设置 formData
function setFormData(formData: Record<string, any>, isIgnoreValidate: boolean = true) {
	// 合并 formData 数据
	Object.assign(modelValue.value, deepClone(formData))

	if (isIgnoreValidate) {
		// 忽略校验
		nextTick(() => {
			getFormRef()?.clearValidate()
		})
	}
}
// 获取 formData
function getFormDataRef() {
	return modelValue.value
}
// 获取 formData 副本
function getFormDataCopy() {
	return deepClone(modelValue.value)
}
// 校验
function validate() {
	return getFormRef()!.validate()
}
/**
 * 重置
 * 这里没用 resetFields 是因为如果有多层嵌套，重置的数据会不可控
 * 用的是获取字典后 modelValue 数据副本，进行克隆赋值
 */
function reset() {
	if (resetData) {
		// 重置数据
		modelValue.value = deepClone(resetData)
	}
	// 嵌套结构 resetFields 方法会有 bug，重置的数据不可控
	// getFormRef()?.resetFields()
	emits('reset')
}

// 传给子节点
provide<CFormProvide>(PROVIDE_TOKEN, { formData: modelValue })

// 暴露给父组件的参数和方法(外部需要什么，都可以从这里暴露出去)
defineExpose({
	getFormRef,
	setFormData,
	getFormDataRef,
	getFormDataCopy,
	validate,
	submit,
	// 外部 reset 和 initQuery不能初始时调用，一定要等 resetData有值后才调用（手动触发那种）
	reset
	// initQuery
})
</script>
<style scoped lang="scss">
// 解决嵌套组件宽度不一致问题
:deep(.el-col) {
	.el-row {
		margin-right: 0 !important;
		margin-left: 0 !important;
		.el-col {
			padding-right: 0 !important;
			padding-left: 0 !important;
		}
	}
}
</style>
